// Copyright (c) 2007-2008, 
// Trevor McCauley, http://www.senocular.com, 
// Alessandro Crugnola, http://www.sephiroth.it,
// Marc Speck, http://www.faindu.com
// All Rights Reserved. The following is Source Code and is subject to all restrictions 
// on such code as contained in the license accompanying this product.
package com.faindu.editImage.rotation
{
	import com.faindu.editImage.EditImage;
	import com.faindu.editImage.base.BaseControl;
	import com.faindu.editImage.base.BaseTool;
	import com.faindu.editImage.base.EditImageEvent;
	import com.faindu.editImage.base.IEditImageTools;
	
	import flash.geom.Matrix;
	import flash.geom.Point;
	
/**
 * Tool that rotates the image around the center of the image. By default, 
 * <code>RotateTool</code> is disabled.
 */
public class RotateTool extends BaseTool implements IRotateTool
{
	//==========================================================================
	//
	//  Controls used in this tool
	//
	//--------------------------------------------------------------------------  
	/** @private */
	protected var rotationControl: BaseControl
	
    //-------------------------------------------------------------------------- 
    //
    //  Properties
    //
    //--------------------------------------------------------------------------  
	//----------------------------------
	//  constrainRotationAngle
	//----------------------------------	
	private var _constrainRotationAngle:Number = 15; // default at 15 degrees
	/**
	 * @inheritDoc
	 * 
	 * @default 15 degrees
	 */
	public function get constrainRotationAngle():Number 
	{
		return _constrainRotationAngle
	}
	public function set constrainRotationAngle( degrees:Number):void 
	{
		if (_constrainRotationAngle != degrees) 
		{
			_constrainRotationAngle = degrees
		}
	}
	
	//----------------------------------
	//  deltaAngle
	//----------------------------------	
	protected var _deltaAngle: Number = 0
	/**
	 * @inheritDoc
	 * 
	 * @see com.faindu.editImage.crop.CropControl.draw()
	 */
	public function get deltaAngle(): Number
	{
		return _deltaAngle
	}
	public function set deltaAngle( value:Number ): void
	{
		_deltaAngle = value
	}
	
    //-------------------------------------------------------------------------- 
    //
    //  Methods
    //
    //--------------------------------------------------------------------------  		
	/**
	 * @inheritDoc
	 */
	override public function init( editImage:EditImage, startInteractionHandler:Function ): void
	{
		// initialize properties
		editImageTools = IEditImageTools( editImage )
		editImage.addEventListener( EditImageEvent.CONTROL_DOWN, startControlInteraction )
		
		// create all controls
		rotationControl = new RotateControl( rotationInteraction )
		
		// initialize all controlls
		rotationControl.init( editImage, startInteractionHandler, this )
	}

    //==========================================================================
    //
    //  Rotation interaction
    //
    //--------------------------------------------------------------------------  	
	/**
	 * Set to <code>distortAngle()</code> in startInteractionHandler 
	 * 
	 * @see #startInteractionHandler()
	 */
 	private var interactionStartAngle:Number = 0;

	/**
	 * The <code>toolMatrix</code> at the start of the interaction.
	 */
	private var interactionStartMatrix: Matrix
	
	private var globalImageCenter: Point 

	private var globalMatrix: Matrix
	
	private var translatedGlobalMatrix: Matrix
	
	private var translatedGlobalInvertedMatrix: Matrix
	
	/**
	 * This is the real angle of the image. Most operations use only relative
	 * rotations. When an absolut rotation is required, <code>realAngle</code>
	 * can be used.
	 */
	private var absoluteStartAngle: Number
	
	/**
	 * @inheritDoc
	 */
	override protected function startControlInteraction( editImageEvent:EditImageEvent ): void
	{
   		globalMatrix = editImageTools.transform.concatenatedMatrix
		globalImageCenter = globalMatrix.transformPoint( editImageTools.controlsPositions.center )
		
		translatedGlobalMatrix = globalMatrix.clone()
		translatedGlobalMatrix.translate( -globalImageCenter.x, -globalImageCenter.y )
		
		translatedGlobalInvertedMatrix = translatedGlobalMatrix.clone()
		translatedGlobalInvertedMatrix.invert()
		
		interactionStartAngle = distortAngle( editImageEvent.mousePosition, globalMatrix, globalImageCenter )
		interactionStartMatrix =  editImageTools.toolMatrix.clone()
		
		absoluteStartAngle = absoluteAngleOfImage(	editImageTools.controlsPositions.topLeft, 
													editImageTools.controlsPositions.bottomLeft,
													globalMatrix )
	}

	/**
	 * Calculate the absolute angle between <code>firstCorner</code> 
	 * and the horizontal line going through <code>origin</code>.
	 * 
	 * @param origin
	 * @param firstCorner
	 * @return angle in rad 
	 */
	protected function absoluteAngleOfImage( origin:Point, firstCorner:Point, globalMatrix:Matrix ): Number
	{
		var imageOrigin: Point = globalMatrix.transformPoint( origin )
		var imageFirstCorner: Point = globalMatrix.transformPoint( firstCorner )
		var leftSide: Point = imageOrigin.subtract( imageFirstCorner )
		return Math.atan2( leftSide.y, leftSide.x ) 
	}

	/**
	 * Control Interaction.  Rotates the image. If the shift key is not pressed,
	 * it rotates with the step of <code>constrainRotationAngle</code>. The angle
	 * is always 0 degrees or a multiple of <code>constrainRotationAngle</code> even
	 * if the image has been rotated to intermediate angle.
	 * 
	 * @see #constrainRotationAngle
	 */
	protected function rotationInteraction( editImageEvent:EditImageEvent ):void 
	{
		var rotatedMatrix: Matrix = interactionStartMatrix.clone()
		
		// rotate in global transform
		rotatedMatrix.concat( translatedGlobalMatrix )
		
		// get change in rotation
		var deltaAngle: Number = distortAngle( editImageEvent.mousePosition, globalMatrix, globalImageCenter )
		
		// constrain rotation based on constrainRotationAngle
 		if ( ! editImageEvent.shiftKey ) 
		{
			// reduce angle to [ -Pi, +Pi ]
			var twoPiMultiple: Number = Math.floor( ( deltaAngle + Math.PI ) / ( 2 * Math.PI ) )	
			deltaAngle -= twoPiMultiple * 2 * Math.PI
			
			var step:Number = constrainRotationAngle * Math.PI/180
			var stepMultiple: Number = Math.round( deltaAngle / step ) 
			deltaAngle = stepMultiple * step - absoluteStartAngle
		}
		else
		{
			deltaAngle -= interactionStartAngle
		}
		this.deltaAngle = deltaAngle
		
		rotatedMatrix.rotate( deltaAngle )
		rotatedMatrix.concat( translatedGlobalInvertedMatrix )
		editImageTools.toolMatrix = rotatedMatrix
	}
		
	/**
	 *  
	 * @param mouseLoc
	 * @param globalMatrix
	 * @param globalImageCenter
	 * @return angle between <code>_mouseLoc</code> and the center of the target 
	 * (taken from <code>controlBounds.center</code>) in radiants.
	 */
	protected function distortAngle( mouseLoc:Point, globalMatrix:Matrix, globalImageCenter:Point ):Number 
	{
		// use global mouse
		var gMouseLoc:Point = globalMatrix.transformPoint( mouseLoc )
		
		// distance and angle of mouse from registration
		var offset:Point = gMouseLoc.subtract( globalImageCenter )
		var angle: Number = Math.atan2( offset.y, offset.x ) 
		return angle
	}
	
	/**
	 * Position the rotate button.
	 */
	override public function draw(): void
	{
		rotationControl.draw()
	}
	
	/**
	 * Calculate <code>deltaAngle</code> for rotating the other controls. 
	 */
	override public function reset(): void
	{
		var globalMatrix:Matrix = editImageTools.transform.concatenatedMatrix
		
		// Note that topRight and topLeft is taken.
		deltaAngle = absoluteAngleOfImage( editImageTools.controlsPositions.topRight, editImageTools.controlsPositions.topLeft, globalMatrix )
		
		rotationControl.reset()
	}
}
}